#!/usr/bin/python
# Copyright 2018 The Tulsi Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Symlinks files generated by Bazel into bazel-tulsi-includes for indexing."""

from __future__ import print_function
import json
import os
import shutil
import sys


class Installer(object):
  """Symlinks generated files into bazel-tulsi-includes."""

  def __init__(self, bazel_exec_root, preserve_tulsi_includes=True,
               output_root=None):
    """Initializes the installer with the proper Bazel paths."""

    self.bazel_exec_root = bazel_exec_root
    self.preserve_tulsi_includes = preserve_tulsi_includes
    # The folder must begin with an underscore as otherwise Bazel will delete
    # it whenever it builds. See tulsi_aspects.bzl for futher explanation.
    if not output_root:
      output_root = bazel_exec_root
    self.tulsi_root = os.path.join(output_root, 'bazel-tulsi-includes')

  def PrepareTulsiIncludes(self):
    """Creates tulsi includes, possibly removing the old folder."""

    tulsi_root = self.tulsi_root
    if not self.preserve_tulsi_includes and os.path.exists(tulsi_root):
      shutil.rmtree(tulsi_root)
    if not os.path.exists(tulsi_root):
      os.mkdir(tulsi_root)

  def InstallForTulsiouts(self, tulsiouts):
    """Creates tulsi includes and symlinks generated sources."""
    self.PrepareTulsiIncludes()

    for file_path in tulsiouts:
      try:
        output_data = json.load(open(file_path))
        self.InstallForData(output_data)
      except (ValueError, IOError) as e:
        print('Failed to load output data file "%s". %s' % (file_path, e))

  def InstallForData(self, output_data):
    """Symlinks generated sources present in the output_data."""
    bazel_exec_root = self.bazel_exec_root
    tulsi_root = self.tulsi_root

    for gs in output_data['generated_sources']:
      real_path, link_path = gs
      src = os.path.join(bazel_exec_root, real_path)

      # Bazel outputs are not guaranteed to be created if nothing references
      # them. This check skips the processing if an output was declared
      # but not created.
      if not os.path.exists(src):
        continue

      # The /x/x/ part is here to match the number of directory components
      # between tulsi root and bazel root. See tulsi_aspects.bzl for futher
      # explanation.
      dst = os.path.join(tulsi_root, 'x/x/', link_path)

      dst_dir = os.path.dirname(dst)
      if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)

      # It's important to use lexists() here in case dst is a broken symlink
      # (in which case exists() would return False).
      if os.path.lexists(dst):
        # Don't need to do anything if the link hasn't changed.
        if os.readlink(dst) == src:
          continue

        # Link changed; must remove it otherwise os.symlink will fail.
        os.unlink(dst)

      os.symlink(src, dst)


if __name__ == '__main__':
  if len(sys.argv) < 3:
    sys.stderr.write('usage: %s <bazel exec root> '
                     '<.tulsiouts JSON files>\n' % sys.argv[0])
    exit(1)

  Installer(sys.argv[1]).InstallForTulsiouts(sys.argv[2:])
